package cs301.msflags;

import java.io.Serializable;
import java.util.Vector;

import android.content.Intent;
import android.graphics.Point;

import cs301.game.GameAction;
import cs301.game.GameComputerPlayer;
import cs301.game.GamePlayer;

/**
 * @author John Olennikov
 * @author Kyanne Mayfield
 * @author Allie Seibert
 * @author Emilia Holbik
 * @version 12/7/2012
 * 
 *           This class is the smart AI, which knows how to play the game and
 *           plays it with an algorithm for the purpose of winning.
 */

public class MSFlagsAdvancedComputerPlayer extends GameComputerPlayer {

	/**
	 * A reference to my copy of the current game state
	 */
	private MSFlagsGame myMSFlagsGame = null;

	/**
	 * A 2-D array of the same dimensions of the Board in the current GameState
	 * designed to record the chance that any given covered space might have a
	 * mine
	 */

	private double[][] record;

	/**
	 * The current board that the record will get its data from.
	 */

	private BoardSpace[][] board;

	/**
	 * The current state of the Board object, used to get the 2-D array of
	 * BoardSpace and for the getNeighbors method.
	 */

	private Board myBoardObject;

	/**
	 * A vector that keeps track of all the clues with surrounding covered
	 * spaces where isMine is unknown.
	 */

	private Vector<Clue> allClues;

	/**
	 * Used in the record array to mark a covered space that is known to be
	 * empty.
	 */

	private static int EMPTY = 0;

	/**
	 * Used in the record array to mark a covered space that is known to contain
	 * a mine.
	 */

	private static int MINE = 1;

	/**
	 * Used in the record array to mark a covered space where it is unkown
	 * whether there is a mine or not.
	 */

	private static int NO_RECORD = -1;

	/**
	 * The source of the AI player, which is always 1.
	 */

	private int aiPlayer;

	/**
	 * Constructor:
	 * Creates the 2-D array of integers called record, recording where mines 
	 * are or might be on the board using the Board class on the current 
	 * GameState.
	 */

	public MSFlagsAdvancedComputerPlayer() {
		super();
	}

	/**
	 * Start the "configure game" activity
	 */
	public void startSetupActivity() {
		Intent setupIntent = new Intent(MSFlagsAdvancedComputerPlayer.this,
				MSFlagsSetupActivity.class);

		// Return true if myMSFlagsGame is created
		Boolean isGameCreated = (myMSFlagsGame != null);

		setupIntent.putExtra("isGameCreated", isGameCreated);

		// Random (150) intent id
		startActivityForResult(setupIntent, 150);

		// Prevents animation when switching activities
		overridePendingTransition(0, 0);
	}

	/**
	 * Gets the user specified board dimensions and number of mines from the
	 * configure board screen, and gives it to the gamestate to reconfigure the
	 * board.
	 * 
	 * @param requestCode  indicates where the activity result came from
	 * @param resultCode  the result code from the activity
	 * @param data  an intent containing the information obtained by the
	 *            activity
	 */
	@Override
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		// extract the game state
		myMSFlagsGame = (MSFlagsGame) this.game;

		MSFlagsMainActivity.showConfig = false;

		// if we are not resuming the game, then get the current configuration
		if (!data.getBooleanExtra("resumeGame", false)) {

			int boardWidth = data.getIntExtra("boardWidth", 20);
			int boardHeight = data.getIntExtra("boardHeight", 20);
			int totalNumMines = data.getIntExtra("totalNumMines", 15);

			// Return this resulting action to the caller
			Intent intent = this.getIntent();
			intent.putExtra(GamePlayer.GAME_ACTION,
					(Serializable) new MSFlagsConfigureBoardAction(
							myMSFlagsGame.whoseTurn(), boardWidth, boardHeight,
							totalNumMines));
			setResult(RESULT_OK, intent);
			finish();
		}
	}

	/**
	 * Using the record made in updateRecord the method chooses a space to
	 * uncover that is least likely to contain a mine. If in the current turn a
	 * space has been uncovered this method calls upDate record the first time
	 * it flags, then flags until there are no more unflagged spaces which are
	 * calculated to have mines.
	 * 
	 * @return  the action telling the game state to update itself
	 */

	@Override
	protected GameAction calculateMove() {

		aiPlayer = this.game.whoseTurn();

		updateRecord();
		Point uncoverable = null;
		// If the AI hasn't yet uncovered this turn, run through the board and
		// return the lowest value in an uncover act.
		for (int x = 0; x < board.length; ++x) {
			for (int y = 0; y < board[0].length; ++y) {
				if (board[x][y].isCovered()) {
					// If the AI has not yet selected a space to uncover or if
					// the probability of a mine is smaller in a different
					// space than currently recorded, record the space with
					// the lower value.
					if (uncoverable == null
							|| record[x][y] < record[uncoverable.x][uncoverable.y]) {
						uncoverable = new Point(x, y);
					}
				}
			}
		}
		
		// Uncover the space we just found. If we have not uncovered a mine,
		// then update the probabilities of a mine for the surrounding spaces.
		myBoardObject.uncoverSpace(uncoverable.x, uncoverable.y);
		if (!myBoardObject.getUncoveredMine()) {
			updateRecord();
		}
		
		// If the AI finds a covered space with a record of EMPTY that is
		// unflagged, it returns a flag action. If no flags are found it will
		// reach the end and return and EndTurnAction.
		boolean hasFlag;
		int flagsRemaining = ((MSFlagsGame)game).getFlagsRemaining(aiPlayer);
		for (int x = 0; x < board.length; ++x) {
			for (int y = 0; y < board[0].length; ++y) {
				if (board[x][y].isCovered() && !board[x][y].isFlagged()
						&& record[x][y] == MINE) {
					hasFlag = (flagsRemaining > 0);
					flagsRemaining += myBoardObject.flaggedByPlayer(aiPlayer,
							hasFlag, x, y);
				}
			}
		}
		// Once finished with the algorithm, return an end turn action
		return new MSFlagsEndTurnAction(aiPlayer, myBoardObject,
				flagsRemaining, myBoardObject.getUncoveredMine());
	}

	/**
	 * Redraws the record board and the allClues vector
	 */
	private void updateRecord() {

		// resets the board object, board of BoardSpaces, record and allClues
		myBoardObject = (Board) ((MSFlagsGame) this.game).getBoard();
		board = myBoardObject.getBoardSpaceArray();
		record = new double[board.length][board[0].length];
		allClues = new Vector<Clue>();

		// draws the record to contain 0 if the space is empty or give the
		// BoardSpaceValue if it's uncovered
		for (int x = 0; x < board.length; x++) {
			for (int y = 0; y < board[x].length; y++) {
				if (board[x][y].isCovered()) {
					record[x][y] = NO_RECORD;
				} else {
					record[x][y] = board[x][y].getSpaceValue();
				}
			}
		}

		// variables for filling the allClues vector
		Vector<Point> containsMine;

		for (int x = 0; x < board.length; x++) {
			for (int y = 0; y < board[0].length; y++) {
				// if there is some probability that there is a mine in 
				// this space
				if (!board[x][y].isCovered() && record[x][y] > EMPTY) {
					;
					containsMine = new Vector<Point>();
					// get the surrounding BoardSpaces for this point
					for (Point p : (myBoardObject.getNeighbors(x, y))) {
						// as precaution, add the neighbors to a contains mine
						// vector; later they will be deleted from the vector
						// if thought not to contain a mine
						if (board[p.x][p.y].isCovered()) {
							containsMine.add(p);
						}
					}
					allClues.add(new Clue(x, y, containsMine));
				}
			}
		}

		// Algorithms that fill the record with the status of the
		// spaces or a probability that the space contains a mine
		clearEasyClues();
		fillAdvancedClues();

		// fills any remaining, unrecorded covered record spaces with the base
		// probability of mines per space
		double minesPerSpace = (double) myBoardObject.getNumMines()
				/ (double) (board.length * board[0].length);
		for (int x = 0; x < board.length; x++) {
			for (int y = 0; y < board[0].length; y++) {
				if (record[x][y] == NO_RECORD) {
					record[x][y] = minesPerSpace;
				}
			}
		}
	}

	/**
	 * Runs through the allClues vectors and removes spaces from the clue's
	 * vector when they contain EMPTY or MINE or removes clues that have empty
	 * vectors from the allClues vector; runs recursively and returns false if
	 * nothing was changed at all
	 * 
	 * @return  whether or not the clues were changed
	 */

	private boolean clearEasyClues() {

		if (allClues.isEmpty()) {
			return false;
		}
		
		boolean wasChanged = false;
		Clue clue;
		Point p;
		int size;
		for (int i = 0; i < allClues.size(); ++i) {
			// get each clue if a mine underneath is unknown
			clue = allClues.elementAt(i);
			if (!clue.getPoints().isEmpty()) {

				// if the record of an unkownSpace is no longer set to NO_RECORD
				// (implying that it is either MINE or EMPTY) it is removed from
				// the clue's vector and subtracted from the clue's record
				for (int j = 0; j < clue.getAllPoints().size(); ++j) {
					p = clue.getAllPoints().elementAt(j);
					if (record[p.x][p.y] != NO_RECORD) {
						wasChanged = true;
						clue.remove(p);
						record[clue.getX()][clue.getY()] -= record[p.x][p.y];
						j--;
					}
				}

				if (!clue.getPoints().isEmpty()) {
					size = 0;
					// if the value of a clue matches the number of spaces in 
					// a clue or equals zero
					if (record[clue.getX()][clue.getY()] == clue.getPoints()
							.size() || record[clue.getX()][clue.getY()] == 0) {
						if (!clue.getPoints().isEmpty())
							size = clue.getPoints().size();
						// distributed the value of the clue across the clues 
						// getPoints spaces
						while (!clue.getPoints().isEmpty()) {
							p = clue.getPoints().elementAt(0);
							record[p.x][p.y] = (record[clue.getX()][clue.getY()] 
									/ size);
							clue.remove(p);
							wasChanged = true;
						}
					}
				}
			}
		}
		
		// if we have iterated through all of the clues for a given BoardSpace,
		// remove that clue from the vector
		for (int i = 0; i < allClues.size(); ++i) {
			if (allClues.elementAt(i).getPoints().isEmpty()) {
				allClues.removeElementAt(i);
				--i;
			}
		}
		
		// if changes where made it calls itself recursively and returns true
		if (wasChanged) {
			clearEasyClues();
		}
		return wasChanged;
	}

	/**
	 * FillAdvancedClues finds the accumulative number of potential mines in a
	 * space over possible spaces based off every remaining clue given
	 */

	private void fillAdvancedClues() {
		if (allClues.isEmpty()) {
			return;
		}
		// initializes two 2d arrays of int's to 0
		double[][] potentialMine = new double[board.length][board[0].length];
		double[][] ambiguousSpace = new double[board.length][board[0].length];

		for (int x = 0; x < board.length; ++x) {
			for (int y = 0; y < board[x].length; ++y) {
				potentialMine[x][y] = 0;
				ambiguousSpace[x][y] = 0;
			}
		}

		// iterates through all the unkownSpaces for every clue, each clue
		// space where a clue still remains will accumulate the total number
		// of mines potentially in the array, and the total number of spaces
		for (Clue c : allClues) {
			for (Point p : c.getPoints()) {
				potentialMine[p.x][p.y] += record[c.getX()][c.getY()];
				ambiguousSpace[p.x][p.y] += c.getPoints().size();
			}
		}

		// if any hint was given as to it's identity, a record slot will be
		// determined by finding sum mines over spaces
		for (int x = 0; x < board.length; ++x) {
			for (int y = 0; y < board[x].length; ++y) {
				if (ambiguousSpace[x][y] != 0) {
					record[x][y] = potentialMine[x][y] / ambiguousSpace[x][y];
				}
			}
		}
	}

	@Override
	protected boolean needConfig() {
		Board myBoardObject = (Board) ((MSFlagsGame) this.game).getBoard();
		return (myBoardObject == null);
	}

	@Override
	protected void doConfig() {
		startSetupActivity();
	}

}